/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
 *
 * This file is part of libxls -- A multiplatform, C library
 * for parsing Excel(TM) files.
 *
 * libxls is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * libxls is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with libxls.  If not, see <http://www.gnu.org/licenses/>.
 *
 * Copyright 2004 Komarov Valery
 * Copyright 2006-2009 Christophe Leitienne
 * Copyright 2008-2009 David Hoerl
 */

#include <config.h>

#include <stdio.h>
#include <stdlib.h>
#include <errno.h>

#include <memory.h>
#include <math.h>
#include <sys/types.h>
#include <string.h>
#include <wchar.h>
#include <assert.h>

#include <xls.h>

#define min(a,b) ((a) < (b) ? (a) : (b))

int xls_debug=0;	// now global, so users can turn it on

static double NumFromRk(BYTE* rk);
extern char* xls_addSheet(xlsWorkBook* pWB,BOUNDSHEET* bs);
extern void xls_addRow(xlsWorkSheet* pWS,ROW* row);
extern void xls_makeTable(xlsWorkSheet* pWS);
extern struct st_cell_data *xls_addCell(xlsWorkSheet* pWS,BOF* bof,BYTE* buf);
extern char *xls_addFont(xlsWorkBook* pWB,FONT* font);
extern void xls_addXF8(xlsWorkBook* pWB,XF8* xf);
extern void xls_addXF5(xlsWorkBook* pWB,XF5* xf);
extern void xls_addColinfo(xlsWorkSheet* pWS,COLINFO* colinfo);
extern void xls_mergedCells(xlsWorkSheet* pWS,BOF* bof,BYTE* buf);
extern void xls_parseWorkBook(xlsWorkBook* pWB);
extern void xls_preparseWorkSheet(xlsWorkSheet* pWS);
extern void xls_formatColumn(xlsWorkSheet* pWS);
extern void xls_parseWorkSheet(xlsWorkSheet* pWS);

extern xlsSummaryInfo *xls_summaryInfo(xlsWorkBook* pWB);
extern void xls_close_summaryInfo(xlsSummaryInfo *pSI);
extern void xls_dumpSummary(char *buf,int isSummary,xlsSummaryInfo	*pSI);

typedef struct {
	uint32_t		format[4];
	uint32_t		offset;
} sectionList;

typedef struct {
	uint16_t		sig;
	uint16_t		_empty;
	uint32_t		os;
	uint32_t		format[4];
	uint32_t		count;
	sectionList		secList[65536];
} header;

typedef struct {
	uint32_t		propertyID;
	uint32_t		sectionOffset;
} propertyList;

typedef struct {
	uint32_t		length;
	uint32_t		numProperties;
	propertyList	properties[65536];
} sectionHeader;

typedef struct {
	uint32_t		propertyID;
	uint32_t		data[65536];
} property;


int xls(void)
{
    return 1;
}

void xls_addSST(xlsWorkBook* pWB,SST* sst,DWORD size)
{
    verbose("xls_addSST");

    pWB->sst.continued=0;
    pWB->sst.lastln=0;
    pWB->sst.lastid=0;
    pWB->sst.lastrt=0;
    pWB->sst.lastsz=0;

    pWB->sst.count = TOBIGL(sst->num);
    pWB->sst.string =(struct str_sst_string *)calloc(pWB->sst.count, sizeof(struct str_sst_string));
    xls_appendSST(pWB,&sst->strings,size-8);
}

void xls_appendSST(xlsWorkBook* pWB,BYTE* buf,DWORD size)
{
    DWORD ln; // String character count
    DWORD ofs; // Current offset in SST buffer
    DWORD rt; // Count or rich text formatting runs
    DWORD sz; // Size of asian phonetic settings block
    BYTE flag; // String flags
    char* ret;

    if (xls_debug) {
	    printf("xls_appendSST %ld\n", size);
    }

	sz = rt = ln = 0;	// kch
    ofs=0;

	while(ofs<size)
    {
        int ln_toread;

        // Restore state when we're in a continue record
        // or read string length
        if (pWB->sst.continued)
        {
            ln=pWB->sst.lastln;
            rt=pWB->sst.lastrt;
            sz=pWB->sst.lastsz;
        }
        else
        {
		    ln = 0;
	        memcpy(&ln,buf+ofs,2);
	        ln=TOBIGL(ln);
            rt = 0;
            sz = 0;

            ofs+=2;
        }

		if (xls_debug) {
        	printf("ln=%ld\n", ln);
		}

        // Read flags
        if (  (!pWB->sst.continued)
            ||(  (pWB->sst.continued)
               &&(ln != 0) ) )
        {
            flag=*(BYTE*)(buf+ofs);
            ofs++;

            // Count of rich text formatting runs
            if (flag & 0x8)
            {
				rt = 0;
	            memcpy(&rt,buf+ofs,2);
		        rt=TOBIGL(rt);
                ofs+=2;
            }

            // Size of asian phonetic settings block
            if (flag & 0x4)
            {
		        memcpy(&sz,buf+ofs,sizeof(sz));
		        sz=TOBIGL(sz);
                ofs+=4;

				if (xls_debug) {
					printf("sz=%ld\n", sz);
				}
            }
        }
        else
        {
            flag = 0;
        }

		// Read characters (compressed or not)
        ln_toread = 0;
        if (ln > 0)
        {
            if (flag & 0x1)
            {
                int new_len = 0;
                ln_toread = min((size-ofs)/2, ln);

                ret=unicode_decode(buf+ofs,ln_toread*2, &new_len,pWB->charset);

                if (ret == NULL)
                {
                    ret = strdup("*failed to decode utf16*");
                    new_len = strlen(ret);
                }

                ret = (char *)realloc(ret,new_len+1);
                *(char*)(ret+new_len)=0;

                ln -= ln_toread;
                ofs+=ln_toread*2;

                if (xls_debug) {
	                printf("String16: %s(%i)\n",ret,new_len);
                }
            }
            else
            {
                ln_toread = min((size-ofs), ln);

                ret = (char *)malloc(ln_toread + 1);
                memcpy (ret, (buf+ofs),ln_toread);
                *(char*)(ret+ln_toread)=0;

                ln -= ln_toread;
                ofs+=ln_toread;

                if (xls_debug) {
                	printf("String8: %s(%li) \n",ret,ln);
                }
            }
        }
        else
        {
         ret = strdup("");
        }

        if (  (ln_toread > 0)
            ||(!pWB->sst.continued) )
        {
            // Concat string if it's a continue, or add string in table
            if (!pWB->sst.continued)
            {
                pWB->sst.lastid++;
                pWB->sst.string[pWB->sst.lastid-1].str=ret;
            }
            else
            {
                char *tmp;
                tmp=pWB->sst.string[pWB->sst.lastid-1].str;
                tmp=(char *)realloc(tmp,strlen(tmp)+strlen(ret)+1);
                pWB->sst.string[pWB->sst.lastid-1].str=tmp;
                memcpy(tmp+strlen(tmp),ret,strlen(ret)+1);
            }

			if (xls_debug) {
	            printf("String % 4ld: %s<end>\n", pWB->sst.lastid-1, pWB->sst.string[pWB->sst.lastid-1].str);
			}
        }

		// Jump list of rich text formatting runs
        if (  (ofs < size)
            &&(rt > 0) )
          {
           int rt_toread = min((size-ofs)/4, rt);
           rt -= rt_toread;
           ofs += rt_toread*4;
          }

		// Jump asian phonetic settings block
        if (  (ofs < size)
            &&(sz > 0) )
          {
           int sz_toread = min((size-ofs), sz);
           sz -= sz_toread;
           ofs += sz_toread;
          }

        pWB->sst.continued=0;
    }

    // Save current character count and count of rich text formatting runs and size of asian phonetic settings block
	if (ln > 0 || rt > 0 || sz > 0) {
		pWB->sst.continued = 1;
		pWB->sst.lastln = ln;
		pWB->sst.lastrt = rt;
		pWB->sst.lastsz = sz;

		if (xls_debug) {
			printf("continued: ln=%ld, rt=%ld, sz=%ld\n", ln, rt, sz);
		}
	}
}

static double NumFromRk(BYTE* rk)
{
    volatile double num; // volatile necessary at least for Cygwin GCC 3.4.4
    DWORD drk;
	memcpy(&drk,rk,sizeof(drk));
	drk=TOBIGL(drk);
    //drk=TOBIGL(*(DWORD*)rk);

	// What kind of value is this ?
    if (drk & 0x02) {
    	// Floating point value;
        num = (double)(drk >> 2);
    } else {
    	// Integer value
#ifdef BE
        *((DWORD *)&num) = drk & 0xfffffffc;
        *(1+(DWORD *)&num) = 0;
#else
        *(1+(DWORD *)&num) = drk & 0xfffffffc;
        *((DWORD *)&num) = 0;
#endif
    }

    // Is value multiplied by 100 ?
    if (drk & 0x01) {
        num = num / 100.0;
    }

    return num;
}


char* xls_addSheet(xlsWorkBook* pWB, BOUNDSHEET *bs)
{
	char* name;
	DWORD filepos;
	BYTE visible, type;

	filepos = TOBIGL(bs->filepos);
	visible = bs->visible;
	type = bs->type;

	// printf("charset=%s uni=%d\n", pWB->charset, unicode);
	// printf("bs name %.*s\n", bs->name[0], bs->name+1);
	name=get_string(bs->name, 0, pWB->is5ver, pWB->charset);
	// printf("name=%s\n", name);

	if(xls_debug) {
		printf ("xls_addSheet\n");
		switch (type & 0x0f)
		{
		case 0x00:
			/* worksheet or dialog sheet */
			printf ("85: Worksheet or dialog sheet\n");
			break;
		case 0x01:
			/* Microsoft Excel 4.0 macro sheet */
			printf ("85: Microsoft Excel 4.0 macro sheet\n");
			break;
		case 0x02:
			/* Chart */
			printf ("85: Chart sheet\n");
			break;
		case 0x06:
			/* Visual Basic module */
			printf ("85: Visual Basic sheet\n");
			break;
		default:
			printf ("???\n");
			break;
		}
		printf("visible: %x\n", visible);
		printf("    Pos: %Xh\n",filepos);
		printf("   type: %.4Xh\n",type);
		printf("   name: %s\n", name);
	}

    if (pWB->sheets.count==0)
    {
        pWB->sheets.sheet=(struct st_sheet_data *) malloc((pWB->sheets.count+1)*sizeof (struct st_sheet_data));
    }
    else
    {
        pWB->sheets.sheet=(struct st_sheet_data *) realloc(pWB->sheets.sheet,(pWB->sheets.count+1)*sizeof (struct st_sheet_data));
    }
    pWB->sheets.sheet[pWB->sheets.count].name=name;
    pWB->sheets.sheet[pWB->sheets.count].filepos=filepos;
    pWB->sheets.sheet[pWB->sheets.count].visibility=visible;
    pWB->sheets.sheet[pWB->sheets.count].type=type;
    pWB->sheets.count++;

	return name;
}


void xls_addRow(xlsWorkSheet* pWS,ROW* row)
{
    struct st_row_data* tmp;

    //verbose ("xls_addRow");

    tmp=&pWS->rows.row[TOBIGS(row->index)];
    tmp->height=TOBIGS(row->height);
    tmp->fcell=TOBIGS(row->fcell);
    tmp->lcell=TOBIGS(row->lcell);
    tmp->flags=TOBIGS(row->flags);
    tmp->xf=TOBIGS(row->xf)&0xfff;
    tmp->xfflags=TOBIGS(row->xf)&0xf000;
    if(xls_debug) xls_showROW(tmp);
}

void xls_makeTable(xlsWorkSheet* pWS)
{
    WORD i,t;
    struct st_row_data* tmp;
    verbose ("xls_makeTable");

    pWS->rows.row=(struct st_row_data *)calloc((pWS->rows.lastrow+1),sizeof(struct st_row_data));

    for (t=0;t<=pWS->rows.lastrow;t++)
    {
        tmp=&pWS->rows.row[t];
        tmp->index=t;
        tmp->fcell=0;
        tmp->lcell=pWS->rows.lastcol;

        tmp->cells.cell=(struct st_cell_data *)calloc((pWS->rows.lastcol+1),sizeof(struct st_cell_data));

        for (i=0;i<=pWS->rows.lastcol;i++)
        {
            tmp->cells.cell[i].col=i;
            tmp->cells.cell[i].row=t;
            tmp->cells.cell[i].width=pWS->defcolwidth;
            tmp->cells.cell[i].xf=0;
            tmp->cells.cell[i].str=NULL;
            tmp->cells.cell[i].d=0;
            tmp->cells.cell[i].l=0;
            tmp->cells.cell[i].ishiden=0;
            tmp->cells.cell[i].colspan=0;
            tmp->cells.cell[i].rowspan=0;
            tmp->cells.cell[i].id=0x201;
            tmp->cells.cell[i].str=NULL;
        }
    }
}

struct st_cell_data *xls_addCell(xlsWorkSheet* pWS,BOF* bof,BYTE* buf)
{
    struct st_cell_data*	cell;
    struct st_row_data*		row;
    int						i;

	verbose ("xls_addCell");

    row=&pWS->rows.row[TOBIGS(((COL*)buf)->row)];
    //cell=&row->cells.cell[((COL*)buf)->col - row->fcell]; DFH - inconsistent
    cell=&row->cells.cell[TOBIGS(((COL*)buf)->col)];
//if(((COL*)buf)->col != cell->col)
//{
//printf("buf->col=%d cell->col=%d  row->fcell=%d\n", ((COL*)buf)->col , cell->col,  row->fcell);
//}
    cell->id=TOBIGS(bof->id);
    cell->xf=TOBIGS(((COL*)buf)->xf);
    switch (bof->id)
    {
    case TOBIGS(0x0006):	//FORMULA
		// test for formula, if
        if (((FORMULA*)buf)->res!=TOBIGS(0xffff)) {
			cell->l=0;
			// if a double, then set double and clear l
            //cell->d=*(double *)&((FORMULA*)buf)->resid;
			cell->d=0;

#ifdef BE
			{
				unsigned char* from_c;
				unsigned char to_c[8];
				int tmp_i = 7;
				from_c=(char*)(buf+6);
				for (i = 7; i >= 0; i--)
				{
					to_c[7-i] = from_c[i];
				}
				memcpy(&cell->d,&to_c[0],sizeof(cell->d));
			}
#else
			{
				cell->d=*((double*)(buf+6));
			}
#endif
			cell->str=xls_getfcell(pWS->workbook,cell);
		} else {
			cell->l = 0xFFFF;
			switch(((FORMULA*)buf)->resid) {
			case 0:		// String
				return cell;	// cell is half complete, get the STRING next record
			case 1:		// Boolean
				cell->d = (double)((FORMULA*)buf)->resdata[2];
				sprintf((cell->str = malloc(5)), "bool");
				break;
			case 2:		// error
				cell->d = (double)((FORMULA*)buf)->resdata[2];
				sprintf((cell->str = malloc(6)), "error");
				break;
			case 3:		// empty string
				cell->str = calloc(1,1);
				break;
			}
		}
        break;
    case TOBIGS(0x00BD):	//MULRK
        for (i=0;i<=TOBIGS(*(WORD *)(buf+(TOBIGS(bof->size)-2)))-TOBIGS(((COL*)buf)->col);i++)
        {
            cell=&row->cells.cell[TOBIGS(((COL*)buf)->col) + i];
            //cell=&row->cells.cell[((COL*)buf)->col - row->fcell + i];  DFH - inconsistent
            //				col=row->cols[i];
            cell->id=TOBIGS(bof->id);
            cell->xf=TOBIGS(*((WORD *)(buf+(4+i*6))));
            cell->d=NumFromRk((BYTE *)(buf+(4+i*6+2)));
            cell->str=xls_getfcell(pWS->workbook,cell);
        }
        break;
    case TOBIGS(0x00BE):	//MULBLANK
        for (i=0;i<=TOBIGS(*(WORD *)(buf+(TOBIGS(bof->size)-2)))-TOBIGS(((COL*)buf)->col);i++)
        {
            cell=&row->cells.cell[TOBIGS(((COL*)buf)->col) + i];
            //cell=&row->cells.cell[((COL*)buf)->col-row->fcell+i];
            //				col=row->cols[i];
            cell->id=TOBIGS(bof->id);
            cell->xf=TOBIGS(*((WORD *)(buf+(4+i*2))));
            cell->str=xls_getfcell(pWS->workbook,cell);
        }
        break;
    case TOBIGS(0x00FD):	//LABELSST
        cell->l=TOBIGS(*(WORD *)&((LABELSST*)buf)->value);
        cell->str=xls_getfcell(pWS->workbook,cell);
		break;
    case TOBIGS(0x027E):	//RK
        cell->d=NumFromRk(((RK*)buf)->value);
        cell->str=xls_getfcell(pWS->workbook,cell);
        break;
    case TOBIGS(0x0201):	//BLANK
        break;
    case TOBIGS(0x0203):	//NUMBER
#ifdef BE
		{
		    BYTE* tmpf_byte;
			BYTE tmpt_byte[8];
			int tmp_i = 0;
			tmpf_byte = (BYTE*)&((BR_NUMBER*)buf)->value;
			for ( i = 7; i >=0; i--)
			{
				tmpt_byte[7 - i] = tmpf_byte[i];
			}
			//cell->d = *(double *)(&tmpt_byte[0]);
			memcpy(&cell->d,&tmpt_byte[0],sizeof(cell->d));
		}
#else
        cell->d=*(double *)&((BR_NUMBER*)buf)->value;
#endif
        cell->str=xls_getfcell(pWS->workbook,cell);
        break;
    case TOBIGS(0x0204):	//LABEL
		cell->l = TOBIGL((long)&((LABEL*)buf)->value);
		cell->str=xls_getfcell(pWS->workbook,cell);
		cell->l = 0;
        break;
    default:
        cell->str=xls_getfcell(pWS->workbook,cell);
        break;
    }

    if (xls_debug) xls_showCell(cell);

	return cell;
}

char *xls_addFont(xlsWorkBook* pWB, FONT* font)
{
    struct st_font_data* tmp;

    verbose("xls_addFont");
    if (pWB->fonts.count==0)
    {
        pWB->fonts.font=(struct st_font_data *) malloc(sizeof(struct st_font_data));
    } else {
        pWB->fonts.font=(struct st_font_data *) realloc(pWB->fonts.font,(pWB->fonts.count+1)*sizeof(struct st_font_data));
    }

    tmp=&pWB->fonts.font[pWB->fonts.count];

    tmp->name=get_string((BYTE*)&font->name, 0, pWB->is5ver, pWB->charset);

    tmp->height=TOBIGS(font->height);
    tmp->flag=TOBIGS(font->flag);
    tmp->color=TOBIGS(font->color);
    tmp->bold=TOBIGS(font->bold);
    tmp->escapement=TOBIGS(font->escapement);
    tmp->underline=font->underline;
    tmp->family=font->family;
    tmp->charset=font->charset;

    //	xls_showFont(tmp);
    pWB->fonts.count++;

	return tmp->name;
}

void xls_addFormat(xlsWorkBook* pWB, FORMAT* format)
{
    struct st_format_data* tmp;

    verbose("xls_addFormat");
    if (pWB->formats.count==0)
    {
        pWB->formats.format=(struct st_format_data *) malloc(sizeof(struct st_format_data));
    } else {
        pWB->formats.format=(struct st_format_data *) realloc(pWB->formats.format,(pWB->formats.count+1)*sizeof(struct st_format_data));
    }

    tmp=&pWB->formats.format[pWB->formats.count];
    tmp->index=TOBIGS(format->index);
    tmp->value=get_string(format->value, !pWB->is5ver, pWB->is5ver, pWB->charset);
    if(xls_debug) xls_showFormat(tmp);
    pWB->formats.count++;
}

void xls_addXF8(xlsWorkBook* pWB,XF8* xf)
{
    struct st_xf_data* tmp;

    verbose("xls_addXF");
    if (pWB->xfs.count==0)
    {
        pWB->xfs.xf=(struct st_xf_data *) malloc(sizeof(struct st_xf_data));
    }
    else
    {
        pWB->xfs.xf=(struct st_xf_data *) realloc(pWB->xfs.xf,(pWB->xfs.count+1)*sizeof(struct st_xf_data));
    }

    tmp=&pWB->xfs.xf[pWB->xfs.count];

    tmp->font=TOBIGS(xf->font);
    tmp->format=TOBIGS(xf->format);
    tmp->type=TOBIGS(xf->type);
    tmp->align=xf->align;
    tmp->rotation=xf->rotation;
    tmp->ident=xf->ident;
    tmp->usedattr=xf->usedattr;
    tmp->linestyle=TOBIGL(xf->linestyle);
    tmp->linecolor=TOBIGL(xf->linecolor);
    tmp->groundcolor=TOBIGS(xf->groundcolor);

    //	xls_showXF(tmp);
    pWB->xfs.count++;
}
void xls_addXF5(xlsWorkBook* pWB,XF5* xf)
{
    struct st_xf_data* tmp;

    verbose("xls_addXF");
    if (pWB->xfs.count==0)
    {
        pWB->xfs.xf=(struct st_xf_data *) malloc(sizeof(struct st_xf_data));
    }
    else
    {
        pWB->xfs.xf=(struct st_xf_data *) realloc(pWB->xfs.xf,(pWB->xfs.count+1)*sizeof(struct st_xf_data));
    }

    tmp=&pWB->xfs.xf[pWB->xfs.count];

    tmp->font=TOBIGS(xf->font);
    tmp->format=TOBIGS(xf->format);
    tmp->type=TOBIGS(xf->type);
    tmp->align=TOBIGS(xf->align);
/*
    tmp->rotation=xf->rotation;
    tmp->ident=xf->ident;
    tmp->usedattr=xf->usedattr;
    tmp->linestyle=xf->linestyle;
    tmp->linecolor=xf->linecolor;
    tmp->groundcolor=xf->groundcolor;
*/

    //	xls_showXF(tmp);
    pWB->xfs.count++;
}

void xls_addColinfo(xlsWorkSheet* pWS,COLINFO* colinfo)
{
    struct st_colinfo_data* tmp;

    verbose("xls_addColinfo");
    if (pWS->colinfo.count==0)
    {
        pWS->colinfo.col=(struct st_colinfo_data *) malloc(sizeof(struct st_colinfo_data));
    }
    else
    {
        pWS->colinfo.col=(struct st_colinfo_data *) realloc(pWS->colinfo.col,(pWS->colinfo.count+1)*sizeof(struct st_colinfo_data));
    }

    tmp=&pWS->colinfo.col[pWS->colinfo.count];
    tmp->first=TOBIGS(colinfo->first);
    tmp->last=TOBIGS(colinfo->last);
    tmp->width=TOBIGS(colinfo->width);
    tmp->xf=TOBIGS(colinfo->xf);
    tmp->flags=TOBIGS(colinfo->flags);

    if(xls_debug) xls_showColinfo(tmp);
    pWS->colinfo.count++;
}

void xls_mergedCells(xlsWorkSheet* pWS,BOF* bof,BYTE* buf)
{
    int count=TOBIGS(*((WORD*)buf));
    int i,c,r;
    struct MERGEDCELLS* span;
    verbose("Merged Cells");
    for (i=0;i<count;i++)
    {
        span=(struct MERGEDCELLS*)(buf+(2+i*sizeof(struct MERGEDCELLS)));
        //		printf("Merged Cells: [%i,%i] [%i,%i] \n",span->colf,span->rowf,span->coll,span->rowl);
        for (r=TOBIGS(span->rowf);r<=TOBIGS(span->rowl);r++)
            for (c=TOBIGS(span->colf);c<=TOBIGS(span->coll);c++)
                pWS->rows.row[r].cells.cell[c].ishiden=1;
        pWS->rows.row[TOBIGS(span->rowf)].cells.cell[TOBIGS(span->colf)].colspan=(TOBIGS(span->coll)-TOBIGS(span->colf)+1);
        pWS->rows.row[TOBIGS(span->rowf)].cells.cell[TOBIGS(span->colf)].rowspan=(TOBIGS(span->rowl)-TOBIGS(span->rowf)+1);
        pWS->rows.row[TOBIGS(span->rowf)].cells.cell[TOBIGS(span->colf)].ishiden=0;
    }
}

void xls_parseWorkBook(xlsWorkBook* pWB)
{
    BOF bof1;
    BOF bof2;
    BYTE* buf;
	BYTE once;
    //DWORD size;

	// this to prevent compiler warnings
	once=0;
	bof2.size = 0;
	bof2.id = 0;
    verbose ("xls_parseWorkBook");
    do
    {
        ole2_read(&bof1, 1, 4, pWB->olestr);
 		if(xls_debug) xls_showBOF(&bof1);

        buf=(BYTE *)malloc(TOBIGS(bof1.size));
        ole2_read(buf, 1, TOBIGS(bof1.size), pWB->olestr);
        switch (bof1.id) {
        case TOBIGS(0x00A):		// EOF
            //verbose("EOF");
            break;
        case TOBIGS(0x809):		// BIFF5-8
			{
				BIFF *b = (BIFF*)buf;
				if (b->ver==TOBIGS(0x600))
					pWB->is5ver=0;
				else
					pWB->is5ver=1;
				pWB->type=TOBIGS(b->type);

				if(xls_debug) {
					printf("version: %s\n", pWB->is5ver ? "BIFF5" : "BIFF8" );
					printf("   type: %.2X\n", pWB->type);
				}
			}
            break;

        case TOBIGS(0x042):		// CODEPAGE
            pWB->codepage=TOBIGS(*(WORD*)buf);
			if(xls_debug) printf("codepage=%x\n", pWB->codepage);
            break;

        case TOBIGS(0x03c):		// CONTINUE
			if(once) {
				if (bof2.id==TOBIGS(0xfc))
					xls_appendSST(pWB,buf,TOBIGS(bof1.size));
				bof1=bof2;
			}
            break;

		case TOBIGS(0x003D):	// WINDOW1
			{
				WIND1 *w = (WIND1*)buf;
				if(xls_debug) {
					printf("WINDOW1: ");
					printf("xWn    : %d\n", TOBIGS(w->xWn)/20);
					printf("yWn    : %d\n", TOBIGS(w->yWn)/20);
					printf("dxWn   : %d\n", TOBIGS(w->dxWn)/20);
					printf("dyWn   : %d\n", TOBIGS(w->dyWn)/20);
					printf("grbit  : %d\n", TOBIGS(w->grbit));
					printf("itabCur: %d\n", TOBIGS(w->itabCur));
					printf("itabFi : %d\n", TOBIGS(w->itabFirst));
					printf("ctabSel: %d\n", TOBIGS(w->ctabSel));
					printf("wTabRat: %d\n", TOBIGS(w->wTabRatio));
				}
			}
			break;

        case TOBIGS(0x0fc):		// SST
            xls_addSST(pWB,(SST*)buf,TOBIGS(bof1.size));
            break;

        case TOBIGS(0x0ff):		// EXTSST
            if(xls_debug) dumpbuf("EXTSST",TOBIGS(bof1.size),buf);
            break;

        case TOBIGS(0x085):		// BOUNDSHEET
			{
				BOUNDSHEET *bs = (BOUNDSHEET *)buf;
				char *s;
				// different for BIFF5 and BIFF8
				s = xls_addSheet(pWB,bs);
			}
            break;

        case TOBIGS(0x0e0):  	// XF
			if(pWB->is5ver) {
				XF5 *xf;
				xf = (XF5 *)buf;

				xls_addXF5(pWB,xf);
				if(xls_debug) {
					printf("   font: %d\n", TOBIGS(xf->font));
					printf(" format: %d\n", TOBIGS(xf->format));
					printf("   type: %.4x\n", TOBIGS(xf->type));
					printf("  align: %.4x\n", TOBIGS(xf->align));
					printf("rotatio: %.4x\n", TOBIGS(xf->color));
					printf("  ident: %.4x\n", TOBIGS(xf->fill));
					printf("usedatt: %.4x\n", TOBIGS(xf->border));
					printf("linesty: %.4x\n", TOBIGS(xf->linestyle));
				}
			} else {
				XF8 *xf;
				xf = (XF8 *)buf;

				xls_addXF8(pWB,xf);
				if(xls_debug) {
					xls_showXF(xf);
				}
			}
            break;

        case TOBIGS(0x031):		// FONT
			{
				char *s;
				FONT *f = (FONT*)buf;
				s = xls_addFont(pWB,f);
				if(xls_debug) {
					printf(" height: %d\n", TOBIGS(f->height));
					printf("   flag: 0x%x\n", TOBIGS(f->flag));
					printf("  color: 0x%x\n", TOBIGS(f->color));
					printf(" weight: %d\n", TOBIGS(f->bold));
					printf("escapem: 0x%x\n", TOBIGS(f->escapement));
					printf("underln: 0x%x\n", f->underline);
					printf(" family: 0x%x\n", f->family);
					printf("charset: 0x%x\n", f->charset);
					if(s) printf("   name: %s\n", s);
				}
			}
			break;

        case TOBIGS(0x41E):		//FORMAT
            xls_addFormat(pWB,(FORMAT*)buf);
            break;

		case TOBIGS(0x0293):	// STYLE
			if(xls_debug) {
				struct { unsigned short idx; unsigned char ident; unsigned char lvl; } *styl;
				styl = (void *)buf;

				printf("    idx: 0x%x\n", styl->idx & 0x07FF);
				if(TOBIGS(styl->idx) & 0x8000) {
					printf("  ident: 0x%x\n", styl->ident);
					printf("  level: 0x%x\n", styl->lvl);
				} else {
					char *s = get_string(&buf[2], 1, pWB->is5ver, pWB->charset);
					printf("  name=%s\n", s);
				}
			}
			break;

		case TOBIGS(0x0092):	// PALETTE
			if(xls_debug > 10) {
				unsigned char *p = buf + 2;
				int idx, len;

				len = TOBIGS(*(unsigned short *)buf);
				for(idx=0; idx<len; ++idx) {
					printf("   Index=0x%2.2x %2.2x%2.2x%2.2x\n", idx+8, p[0], p[1], p[2] );
					p += 4;
				}
			}
			break;

		case TOBIGS(0x0022): // 1904
			if(xls_debug) {
				printf("   mode: 0x%x\n", TOBIGS(*(unsigned short *)buf));
			}
			break;

        default:
			if(xls_debug) printf("UNKNOWN BOF=%Xh\n", TOBIGS(bof1.id));
            break;
        }
		free(buf);

        bof2=bof1;
		once=1;

    }
    while ((!pWB->olestr->eof)&&(bof1.id!=TOBIGS(0x0A)));
}


void xls_preparseWorkSheet(xlsWorkSheet* pWS)
{
    BOF tmp;
    BYTE* buf;

    verbose ("xls_preparseWorkSheet");

    ole2_seek(pWS->workbook->olestr,pWS->filepos);
    do
    {
        ole2_read(&tmp, 1,4,pWS->workbook->olestr);
        buf=(BYTE *)malloc(TOBIGS(tmp.size));
        ole2_read(buf, 1,TOBIGS(tmp.size),pWS->workbook->olestr);
        //	xls_showBOF(&tmp);
		//printf("tmp.id=0X%X\n",TOBIGS(tmp.id));
        switch (tmp.id)
        {
        case TOBIGS(0x55):     //DEFCOLWIDTH
            pWS->defcolwidth=TOBIGS(*(WORD*)buf*256);
            break;
        case TOBIGS(0x7D):     //COLINFO
            xls_addColinfo(pWS,(COLINFO*)buf);
            break;
        case TOBIGS(0x208):		//ROW
            if (pWS->rows.lastcol<TOBIGS(((ROW*)buf)->lcell))
                pWS->rows.lastcol=TOBIGS(((ROW*)buf)->lcell);
            if (pWS->rows.lastrow<TOBIGS(((ROW*)buf)->index))
                pWS->rows.lastrow=TOBIGS(((ROW*)buf)->index);
            break;
        }
        free(buf);
    }
    while ((!pWS->workbook->olestr->eof)&&(tmp.id!=TOBIGS(0x0A)));
}

void xls_formatColumn(xlsWorkSheet* pWS)
{
    DWORD i,t,ii;
    DWORD fcol,lcol;

    for (i=0;i<pWS->colinfo.count;i++)
    {
        if (pWS->colinfo.col[i].first<=pWS->rows.lastcol)
            fcol=pWS->colinfo.col[i].first;
        else
            fcol=pWS->rows.lastcol;

        if (pWS->colinfo.col[i].last<=pWS->rows.lastcol)
            lcol=pWS->colinfo.col[i].last;
        else
            lcol=pWS->rows.lastcol;

        for (t=fcol;t<=lcol;t++) {
            for (ii=0;ii<=pWS->rows.lastrow;ii++)
            {
                if (pWS->colinfo.col[i].flags&1)
                    pWS->rows.row[ii].cells.cell[t].ishiden=1;
                pWS->rows.row[ii].cells.cell[t].width=pWS->colinfo.col[i].width;
            }
        }
    }
}

void xls_parseWorkSheet(xlsWorkSheet* pWS)
{
    BOF tmp;
    BYTE* buf;
	struct st_cell_data *cell;
	xlsWorkBook *pWB = pWS->workbook;
    verbose ("xls_parseWorkSheet");

    xls_preparseWorkSheet(pWS);
	// printf("size=%d fatpos=%d)\n", pWS->workbook->olestr->size, pWS->workbook->olestr->fatpos);

    xls_makeTable(pWS);
    xls_formatColumn(pWS);

	cell = (void *)0;
    ole2_seek(pWS->workbook->olestr,pWS->filepos);
    do
    {
        ole2_read(&tmp, 1,4,pWS->workbook->olestr);
        buf=(BYTE *)malloc(TOBIGS(tmp.size));
        ole2_read(buf, 1,TOBIGS(tmp.size),pWS->workbook->olestr);

		// xls_showBOF(&tmp);
        switch (tmp.id)
        {
        case TOBIGS(0x0A):		//EOF
            break;
        case TOBIGS(0x0E5): 	//MERGEDCELLS
            xls_mergedCells(pWS,&tmp,buf);
            break;
        case TOBIGS(0x208):		//ROW
            xls_addRow(pWS,(ROW*)buf);
            break;
        case TOBIGS(0x20B):		//INDEX
            break;
        case TOBIGS(0x0BD):		//MULRK
        case TOBIGS(0x0BE):		//MULBLANK
        case TOBIGS(0x203):		//NUMBER
        case TOBIGS(0x27e):		//RK
        case TOBIGS(0xFD):		//LABELSST
        case TOBIGS(0x201):		//BLANK
        case TOBIGS(0x204):		//LABEL
        case TOBIGS(0x06):		//FORMULA
            cell = xls_addCell(pWS,&tmp,buf);
            break;
		case TOBIGS(0x207):		//STRING, only follows a formula
			if(cell && cell->id == 0x06) { // formula
				cell->str = get_string(buf, !pWB->is5ver, pWB->is5ver, pWB->charset);
				if (xls_debug) xls_showCell(cell);
			}
			break;
#if 0 // debugging
		case 0x023E:	// WINDOW2
			if(xls_debug) {
				printf("WINDOW2: ");
				unsigned short xx, *foo = (void *)buf;

				for(xx=0; xx<7; ++xx, ++foo) {
					printf("0x%4.4x ", *foo);
				}
				printf("\n");
			}
			break;
#endif
        default:
            break;
        }
        free(buf);
    }
    while ((!pWS->workbook->olestr->eof)&&(tmp.id!=TOBIGS(0x0A)));  // 0x0A == EOF
}

xlsWorkSheet * xls_getWorkSheet(xlsWorkBook* pWB,int num)
{
    xlsWorkSheet * pWS;
    verbose ("xls_getWorkSheet");
    pWS=(xlsWorkSheet *)calloc(1, sizeof(xlsWorkSheet));
    pWS->filepos=pWB->sheets.sheet[num].filepos;
    pWS->workbook=pWB;
    pWS->rows.lastcol=0;
    pWS->rows.lastrow=0;
    pWS->colinfo.count=0;
    return(pWS);
}

xlsWorkBook* xls_open(char *file,char* charset)
{
    xlsWorkBook* pWB;
    OLE2*		ole;

    pWB=(xlsWorkBook*)calloc(1, sizeof(xlsWorkBook));
    verbose ("xls_open");

    // open excel file
    if (!(ole=ole2_open(file, charset)))
    {
        if(xls_debug) printf("File \"%s\" not found\n",file);
        return(NULL);
    }

    if ((pWB->olestr=ole2_fopen(ole,"\005SummaryInformation")))
    {
        pWB->summary = calloc(1,4096);
		ole2_read(pWB->summary, 4096, 1, pWB->olestr);
		ole2_fclose(pWB->olestr);
	}

    if ((pWB->olestr=ole2_fopen(ole,"\005DocumentSummaryInformation")))
    {
        pWB->docSummary = calloc(1,4096);
		ole2_read(pWB->docSummary, 4096, 1, pWB->olestr);
		ole2_fclose(pWB->olestr);
	}

    // open Workbook
    if (!(pWB->olestr=ole2_fopen(ole,"Workbook")) && !(pWB->olestr=ole2_fopen(ole,"Book")))
    {
        if(xls_debug) printf("Workbook not found\n");
        ole2_close(ole);
        return(NULL);
    }

    pWB->sheets.count=0;
    pWB->xfs.count=0;
    pWB->fonts.count=0;
    pWB->charset = (char *)malloc(strlen(charset) * sizeof(char));
    strcpy(pWB->charset, charset);
    xls_parseWorkBook(pWB);

    return(pWB);
}

xlsRow *xls_row(xlsWorkSheet* pWS, WORD cellRow)
{
    struct st_row_data *row;

    if(cellRow > pWS->rows.lastrow) return NULL;
    row = &pWS->rows.row[cellRow];

    return row;
}

xlsCell	*xls_cell(xlsWorkSheet* pWS, WORD cellRow, WORD cellCol)
{
    struct st_row_data	*row;

    if(cellRow > pWS->rows.lastrow) return NULL;
    row = &pWS->rows.row[cellRow];
    if(cellCol >= row->lcell) return NULL;

    return &row->cells.cell[cellCol];
}

void xls_close_WB(xlsWorkBook* pWB)
{
	OLE2*		ole;

	verbose ("xls_close");

    // OLE first
	ole=pWB->olestr->ole;
	ole2_fclose(pWB->olestr);
	ole2_close(ole);

    // WorkBook
    free(pWB->charset);

    // Sheets
    {
        DWORD i;
        for(i=0; i<pWB->sheets.count; ++i) {
            free(pWB->sheets.sheet[i].name);
        }
        free(pWB->sheets.sheet);
    }

    // SST
    {
        DWORD i;
        for(i=0; i<pWB->sst.count; ++i) {
            free(pWB->sst.string[i].str);
        }
        free(pWB->sst.string);
    }

    // xfs
    {
        free(pWB->xfs.xf);
    }

    // fonts
    {
        DWORD i;
        for(i=0; i<pWB->fonts.count; ++i) {
            free(pWB->fonts.font[i].name);
        }
        free(pWB->fonts.font);
    }

    // formats
    {
        DWORD i;
        for(i=0; i<pWB->formats.count; ++i) {
            free(pWB->formats.format[i].value);
        }
        free(pWB->formats.format);
    }

    // buffers
	if(pWB->summary)  free(pWB->summary);
	if(pWB->docSummary) free(pWB->docSummary);

	// TODO - free other dynamically allocated objects like string table??
	free(pWB);
}

void xls_close_WS(xlsWorkSheet* pWS)
{
//    st_colinfo	colinfo;

    // ROWS
    {
        DWORD i, j;
        for(j=0; j<pWS->rows.lastrow; ++j) {
            struct st_row_data *row = &pWS->rows.row[j];
            for(i=0; i<row->cells.count; ++i) {
                free(row->cells.cell[i].str);
            }
            free(row->cells.cell);
        }
        free(pWS->rows.row);

    }

    // COLINFO
    {
        free(pWS->colinfo.col);
    }
    free(pWS);
}

const char* xls_getVersion(void)
{
    return PACKAGE_VERSION;
}

//
// http://poi.apache.org/hpsf/internals.html
// or google "DocumentSummaryInformation and UserDefined Property Sets" and look for MSDN hits
//

xlsSummaryInfo *xls_summaryInfo(xlsWorkBook* pWB)
{
	xlsSummaryInfo	*pSI;

	pSI = (xlsSummaryInfo *)calloc(1, sizeof(xlsSummaryInfo));
	xls_dumpSummary(pWB->summary, 1, pSI);
	xls_dumpSummary(pWB->docSummary, 0, pSI);

	return pSI;
}

void xls_close_summaryInfo(xlsSummaryInfo *pSI)
{
	if(pSI->title)		free(pSI->title);
	if(pSI->subject)	free(pSI->subject);
	if(pSI->author)		free(pSI->author);
	if(pSI->keywords)	free(pSI->keywords);
	if(pSI->comment)	free(pSI->comment);
	if(pSI->lastAuthor)	free(pSI->lastAuthor);
	if(pSI->appName)	free(pSI->appName);
	if(pSI->category)	free(pSI->category);
	if(pSI->manager)	free(pSI->manager);
	if(pSI->company)	free(pSI->company);

	free(pSI);
}

void xls_dumpSummary(char *buf,int isSummary,xlsSummaryInfo *pSI) {
	header			*head;
	sectionList		*secList;
	propertyList	*plist;
	sectionHeader	*secHead;
	property		*prop;
	uint32_t i, j;

	if(!buf) return;	// perhaps the document was missing??

	head = (header *)buf;
	//printf("header: \n");
	//printf("  sig=%x\n", head->sig);
	//printf("  os=%x\n", head->os >> 16);
	//printf("  class=%8.8x%8.8x%8.8x%8.8x\n", head->format[0], head->format[1], head->format[2], head->format[3]);
	//printf("  count=%x\n", head->count);

	for(i=0; i<head->count; ++i) {
		secList = &head->secList[i];
		//printf("Section %d:\n", i);
		//printf("  class=%8.8x%8.8x%8.8x%8.8x\n", secList->format[0], secList->format[1], secList->format[2], secList->format[3]);
		//printf("  offset=%d (now at %ld\n", secList->offset, (char *)secList - (char *)buf + sizeof(sectionList));


		secHead = (sectionHeader *)((char *)head + secList->offset);
		//printf("  len=%d\n", secHead->length);
		//printf("  properties=%d\n", secHead->numProperties);
		for(j=0; j<secHead->numProperties; ++j) {
			char **s;

			plist = &secHead->properties[j];
			//printf("      ---------\n");
			//printf("      propID=%d offset=%d\n", plist->propertyID, plist->sectionOffset);
			prop = (property *)((char *)secHead + plist->sectionOffset);
			//printf("      propType=%d\n", prop->propertyID);

			switch(prop->propertyID) {
			case 2:
				//printf("      shortVal=%x\n", *(uint16_t *)prop->data);
				break;
			case 3:
				//printf("      wordVal=%x\n", *(uint32_t *)prop->data);
				break;
			case 30:
				//printf("      longVal=%llx\n", *(uint64_t *)prop->data);
				//printf("      s[%u]=%s\n", *(uint32_t  *)prop->data, (char *)prop->data + 4);
				if(isSummary) {
					switch(plist->propertyID) {
					case 2:		s = &pSI->title;		break;
					case 3:		s = &pSI->subject;		break;
					case 4:		s = &pSI->author;		break;
					case 5:		s = &pSI->keywords;		break;
					case 6:		s = &pSI->comment;		break;
					case 8:		s = &pSI->lastAuthor;	break;
					case 18:	s = &pSI->appName;		break;
					default:	s = NULL;				break;
					}
				} else {
					switch(plist->propertyID) {
					case 2:		s = &pSI->category;		break;
					case 14:	s = &pSI->manager;		break;
					case 15:	s = &pSI->company;		break;
					default:	s = NULL;				break;
					}
				}
				if(s) *s = strdup((char *)prop->data + 4);
				break;
			case 64:
				//printf("      longVal=%llx\n", *(uint64_t *)prop->data);
				break;
			case 65:
#if 0
				{
				uint32_t k;
				for(k=0; k<*(uint32_t  *)prop->data; ++k) {
				unsigned char *t = (unsigned char *)prop->data + 4 + k;
				printf(" %2.2x(%c)", *t, *t);
				}
				printf("\n");
				}
#endif
				break;
			default:
				//printf("      UNKNOWN!\n");
				break;
			}
		}
	}
}

